package cn.momoky.ccool.utils;

import lombok.AllArgsConstructor;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Component
@AllArgsConstructor
public class RedisCommand {

    RedisTemplate<String, Object> template;

    public boolean exists(Object key) {
        return template.hasKey(key.toString());
    }

    public <T> void set(String key, T value) {
        template.opsForValue().set(key, value);
    }

    public <T> void set(String key, T value, long timeout, TimeUnit timeUnit) {
        template.opsForValue().set(key, value, timeout, timeUnit);
    }

    @SuppressWarnings("all")
    public <T> T get(String key) {
        return (T) template.opsForValue().get(key);
    }

    public <T> void mset(Map<String, T> map) {
        template.opsForValue().multiSet(map);
    }

    @SuppressWarnings("all")
    public <T> void mset(Map<String, T> map, long timeout, TimeUnit timeUnit) {
        template.executePipelined(new SessionCallback<Object>() {
            @Override
            public <K, V> Object execute(RedisOperations<K, V> redisOperations) throws DataAccessException {
                ValueOperations<String, Object> ops = (ValueOperations<String, Object>) redisOperations.opsForValue();
                map.forEach((k, v) -> {
                    ops.set(k, v, timeout, timeUnit);
                });
                return null;
            }
        });
    }

    @SuppressWarnings("all")
    public <T> List<T> mget(Collection<String> keys) {
        return Objects.requireNonNull(template.opsForValue().multiGet(keys)).stream().map(o -> (T) o).collect(Collectors.toList());
    }

    public Long incr(String key) {
        return template.opsForValue().increment(key);
    }

    public void incrBy(String key, double value) {
        template.opsForValue().increment(key, value);
    }

    public <K, V> void hset(String key, K hk, V hv) {
        template.opsForHash().put(key, hk, hv);
    }

    @SuppressWarnings("all")
    public <K, V> V hget(String key, K hk) {
        return (V) template.opsForHash().get(key, hk);
    }

    @SuppressWarnings("all")
    public Map<String, Object> hget(String key) {
        Map<Object, Object> entries = template.opsForHash().entries(key);
        Map<String, Object> map = new HashMap<>();
        entries.forEach((k, v) -> {
            map.put((String) k, v);
        });
        return map;
    }

    @SuppressWarnings("all")
    public <K, V> void hset(String key, Collection<Entry<K, V>> entries) {
        template.executePipelined(new SessionCallback<Object>() {
            @Override
            public <K1, V1> Object execute(RedisOperations<K1, V1> redisOperations) throws DataAccessException {
                HashOperations<String, Object, Object> ops = (HashOperations<String, Object, Object>) redisOperations.opsForHash();
                entries.forEach(entrie -> {
                    ops.put(key, entrie.getKey(), entrie.getValue());
                });
                return null;
            }
        });
    }

    @SuppressWarnings("all")
    public <K, V> void hmset(Map<String, Map<K, V>> map) {
        template.executePipelined(new SessionCallback<Object>() {
            @Override
            public <K1, V1> Object execute(RedisOperations<K1, V1> redisOperations) throws DataAccessException {
                HashOperations<String, Object, Object> ops = (HashOperations<String, Object, Object>) redisOperations.opsForHash();
                map.forEach((key, hasmap) -> {
                    ops.putAll(key, hasmap);
                });
                return null;
            }
        });
    }

    @SuppressWarnings("all")
    public <K, V> List<Map<K, V>> hmget(List<String> keys) {
        return template.executePipelined(new SessionCallback<Object>() {
            @Override
            public <K1, V1> Object execute(RedisOperations<K1, V1> redisOperations) throws DataAccessException {
                HashOperations<String, Object, Object> ops = (HashOperations<String, Object, Object>) redisOperations.opsForHash();
                keys.forEach(key -> {
                    ops.entries(key);
                });
                return null;
            }
        }).stream().map(o -> {
            Map<Object, Object> map = (Map<Object, Object>) o;
            Map<K, V> m = new HashMap<>();
            map.forEach((k, v) -> {
                m.put((K) k, (V) v);
            });
            return m;
        }).collect(Collectors.toList());
    }

    @SuppressWarnings("all")
    public <K, V> List<Map<K, V>> hmget(Set<String> keys) {
        return template.executePipelined(new SessionCallback<Object>() {
            @Override
            public <K1, V1> Object execute(RedisOperations<K1, V1> redisOperations) throws DataAccessException {
                HashOperations<String, Object, Object> ops = (HashOperations<String, Object, Object>) redisOperations.opsForHash();
                keys.forEach(key -> {
                    ops.entries(key);
                });
                return null;
            }
        }).stream().map(o -> {
            Map<Object, Object> map = (Map<Object, Object>) o;
            Map<K, V> m = new HashMap<>();
            map.forEach((k, v) -> {
                m.put((K) k, (V) v);
            });
            return m;
        }).collect(Collectors.toList());
    }

    public <K, V> void hincr(String key, K hk, long value) {
        template.opsForHash().increment(key, hk, value);
    }

    public <K, V> void hincr(String key, K hk, double value) {
        template.opsForHash().increment(key, hk, value);
    }

    public <T> void sadd(String key, T value) {
        template.opsForSet().add(key, value);
    }

    public <T> void sadd(String key, Collection<T> values) {
        template.opsForSet().add(key, values);

    }

    public <T> List<T> randomMembers(String key, long len, Class<T> clazz) {
        List<Object> list = template.opsForSet().randomMembers(key, len);
        return list.stream().map(o -> {
            if (o instanceof Integer && Long.class.isAssignableFrom(clazz)) {
                o = Long.valueOf(((Integer) o).toString());
            }
            return (T) o;
        }).collect(Collectors.toList());
    }

    public <T> void lpush(String key, T value) {
        template.opsForList().leftPush(key, value);
    }

    public <T> void lpush(String key, List<T> list) {
        template.opsForList().leftPush(key, list);
    }

    public <T> T lpop(String key) {
        return (T) template.opsForList().leftPop(key);
    }

    public <T> void rpush(String key, T value) {
        template.opsForList().rightPush(key, value);
    }

    public <T> T rpop(String key) {
        return (T) template.opsForList().rightPop(key);
    }

    public <T> List<T> lrange(String key, long start, long stop, Class<T> clazz) {
        return template.opsForList().range(key, start, stop).stream().map(o -> {
            if (o instanceof Integer && Long.class.isAssignableFrom(clazz)) {
                return (T) Long.valueOf(o.toString());
            }
            return (T) o;
        }).collect(Collectors.toList());
    }

    public <T> Long llen(String key) {
        return template.opsForList().size(key);
    }

    public <T> void llrem(String key, long size) {
        template.opsForList().trim(key, 0, size - 1);
    }

    public <T> void lrrem(String key, long size) {
        template.execute(new SessionCallback<Object>() {
            @Override
            public <K, V> Object execute(RedisOperations<K, V> redisOperations) throws DataAccessException {
                ListOperations<String, Object> ops = (ListOperations<String, Object>) redisOperations.opsForList();
                Long len = ops.size(key);
                ops.trim(key, 0, len - size);
                return null;
            }
        });
    }

    @SuppressWarnings("all")
    public <T> Set<T> smembers(String key, Class<T> clazz) {
        return Objects.requireNonNull(template.opsForSet().members(key)).stream().map(o -> {
            if (o instanceof Integer && Long.class.isAssignableFrom(clazz)) {
                o = Long.valueOf(((Integer) o).toString());
            }
            return (T) o;
        }).collect(Collectors.toSet());
    }

    public <T> Long srem(String key, T value) {
        return template.opsForSet().remove(key, value);
    }

    public <T> Long srem(String key, Collection<T> values) {
        return template.opsForSet().add(key, values);
    }

    public Long scard(String key) {
        return template.opsForSet().size(key);
    }

    public <T> void zadd(String key, T value, double score) {
        template.opsForZSet().add(key, value, score);
    }

    @SuppressWarnings("all")
    public <T> Set<T> zrange(String key, long start, long stop, Class<T> clazz) {
        return Objects.requireNonNull(template.opsForZSet().range(key, start, stop)).stream().map(o -> {
            if (o instanceof Integer && Long.class.isAssignableFrom(clazz)) {
                o = Long.valueOf(((Integer) o).toString());
            }
            return (T) o;
        }).collect(Collectors.toSet());
    }

    @SuppressWarnings("all")
    public <T> List<T> zrange(String key, Class<T> clazz) {
        return Objects.requireNonNull(template.opsForZSet().range(key, 0, -1)).stream().map(o -> {
            if (o instanceof Integer && Long.class.isAssignableFrom(clazz)) {
                o = Long.valueOf(((Integer) o).toString());
            }
            return (T) o;
        }).collect(Collectors.toList());
    }

    @SuppressWarnings("all")
    public <T> List<T> zrevrange(String key, long start, long stop, Class<T> clazz) {
        return Objects.requireNonNull(template
                .opsForZSet()
                .reverseRange(key, start, stop)
        ).stream().map(o -> {
            if (o instanceof Integer && Long.class.isAssignableFrom(clazz)) {
                o = Long.valueOf(((Integer) o).toString());
            }
            return (T) o;
        }).collect(Collectors.toList());
    }

    @SuppressWarnings("all")
    public <T> List<T> zrevrange(String key, Class<T> clazz) {
        return Objects.requireNonNull(template.opsForZSet().reverseRange(key, 0, -1)).stream().map(o -> {
            if (o instanceof Integer && Long.class.isAssignableFrom(clazz)) {
                o = Long.valueOf(((Integer) o).toString());
            }
            return (T) o;
        }).collect(Collectors.toList());
    }

    public <T> Long zrem(String key, T value) {
        return template.opsForZSet().remove(key, value);
    }

    public <T> Long zrem(String key, Collection<T> values) {
        return template.opsForZSet().remove(key, values);
    }

    public Long zcount(String key, double min, double max) {
        return template.opsForZSet().count(key,min, max);
    }

    public Long zcount(String key) {
        return template.opsForZSet().count(key,Double.MIN_VALUE, Double.MAX_VALUE);
    }

    public void expire(String key, long timeout, TimeUnit timeUnit) {
        template.expire(key, timeout, timeUnit);
    }

    public void del(String key) {
        template.delete(key);
    }

    public void del(Collection<String> keys) {
        template.delete(keys);

    }

    @SuppressWarnings("all")
    public List<Object> executePipelined(ExecutePipelined ex) {

        List<Object> objects = template.executePipelined(new SessionCallback<Object>() {
            @Override
            public <K, V> Object execute(RedisOperations<K, V> redisOperations) throws DataAccessException {
                RedisOperations<String, Object> ops = (RedisOperations<String, Object>) redisOperations;
                ex.setOps(ops);
                ex.executePipelined();
                return null;
            }
        });

        return objects;
    }

    @SuppressWarnings("all")
    public void execute(Execute e) {
        template.execute(new SessionCallback<Object>() {
            @Override
            public <K, V> Object execute(RedisOperations<K, V> redisOperations) throws DataAccessException {
                RedisOperations<String, Object> ops = (RedisOperations<String, Object>) redisOperations;
                e.setOps(ops);
                e.execute();
                return null;
            }
        });
    }

    public static abstract class Execute extends Command {
        public abstract void execute();
    }

    public static abstract class ExecutePipelined extends Command {
        public abstract <T> List<T> executePipelined();
    }

    public static class Command {
        private RedisOperations<String, Object> ops;

        public void setOps(RedisOperations<String, Object> ops) {
            this.ops = ops;
        }

        public <T> void set(String key, T value) {
            ops.opsForValue().set(key, value);
        }

        public <T> void set(String key, T value, long timeout, TimeUnit timeUnit) {
            ops.opsForValue().set(key, value, timeout, timeUnit);
        }

        @SuppressWarnings("all")
        public <T> T get(String key) {
            return (T) ops.opsForValue().get(key);
        }

        public <T> void mset(Map<String, T> map) {
            ops.opsForValue().multiSet(map);
        }


        @SuppressWarnings("all")
        public <T> List<T> mget(Collection<String> keys) {
            return Objects.requireNonNull(ops.opsForValue().multiGet(keys)).stream().map(o -> (T) o).collect(Collectors.toList());
        }

        public void incr(String key) {
            ops.opsForValue().increment(key);
        }

        public void incrBy(String key, double value) {
            ops.opsForValue().increment(key, value);
        }

        public <K, V> void hset(String key, K hk, V hv) {
            ops.opsForHash().put(key, hk, hv);
        }

        @SuppressWarnings("all")
        public <K, V> V hget(String key, K hk) {
            return (V) ops.opsForHash().get(key, hk);
        }

        @SuppressWarnings("all")
        public Map<String, Object> hget(String key) {
            Map<Object, Object> entries = ops.opsForHash().entries(key);
            Map<String, Object> map = new HashMap<>();
            entries.forEach((k, v) -> {
                map.put((String) k, v);
            });
            return map;
        }

        @SuppressWarnings("all")
        public <K, V> List<Map<K, V>> hmget(List<String> keys) {
            HashOperations<String, Object, Object> hashOps = ops.opsForHash();
            return keys.stream().map(key -> {
                Map<Object, Object> entries = hashOps.entries(key);
                Map<K, V> map = new HashMap<>();
                entries.forEach((k, v) -> {
                    map.put((K) k, (V) v);
                });
                return map;
            }).collect(Collectors.toList());
        }

        public <K, V> long hincr(String key, K hk, long value) {
            return ops.opsForHash().increment(key, hk, value);
        }

        public <K, V> void hincr(String key, K hk, double value) {
            ops.opsForHash().increment(key, hk, value);
        }

        public <T> void lpush(String key, T value) {
            ops.opsForList().leftPush(key, value);
        }

        public <T> void lpush(String key, List<T> list) {
            ops.opsForList().leftPush(key, list);
        }

        public <T> T lpop(String key) {
            return (T) ops.opsForList().leftPop(key);
        }

        public <T> void rpush(String key, T value) {
            ops.opsForList().rightPush(key, value);
        }

        public <T> T rpop(String key) {
            return (T) ops.opsForList().rightPop(key);
        }

        public <T> List<T> lrange(String key, long start, long stop, Class<T> clazz) {
            return ops.opsForList().range(key, start, stop).stream().map(o -> {
                if (o instanceof Integer && Long.class.isAssignableFrom(clazz)) {
                    return (T) Long.valueOf(o.toString());
                }
                return (T) o;
            }).collect(Collectors.toList());
        }

        public <T> Long llen(String key) {
            return ops.opsForList().size(key);
        }

        public <T> void llrem(String key, long size) {
            ops.opsForList().trim(key, 0, size - 1);
        }

        public <T> void sadd(String key, T value) {
            ops.opsForSet().add(key, value);
        }

        public <T> void sadd(String key, Collection<T> values) {
            ops.opsForSet().add(key, values);
        }

        @SuppressWarnings("all")
        public <T> Set<T> members(String key) {
            return Objects.requireNonNull(ops.opsForSet().members(key)).stream().map(o -> (T) o).collect(Collectors.toSet());
        }

        public <T> Long srem(String key, T value) {
            return ops.opsForSet().add(key, value);
        }

        public <T> Long srem(String key, Collection<T> values) {
            return ops.opsForSet().add(key, values);
        }

        public Long scard(String key) {
            return ops.opsForSet().size(key);
        }

        public <T> void zadd(String key, T value, double score) {
            ops.opsForZSet().add(key, value, score);
        }

        @SuppressWarnings("all")
        public <T> Set<T> zrange(String key, long start, long stop) {
            return Objects.requireNonNull(ops.opsForZSet().range(key, start, stop)).stream().map(o -> (T) o).collect(Collectors.toSet());
        }

        @SuppressWarnings("all")
        public <T> Set<T> zrange(String key) {
            return Objects.requireNonNull(ops.opsForZSet().range(key, 0, -1)).stream().map(o -> (T) o).collect(Collectors.toSet());
        }

        @SuppressWarnings("all")
        public <T> Set<T> zrevrange(String key, long start, long stop) {
            return Objects.requireNonNull(ops.opsForZSet().reverseRange(key, start, stop)).stream().map(o -> (T) o).collect(Collectors.toSet());
        }

        @SuppressWarnings("all")
        public <T> Set<T> zrevrange(String key) {
            return Objects.requireNonNull(ops.opsForZSet().reverseRange(key, 0, -1)).stream().map(o -> (T) o).collect(Collectors.toSet());
        }

        public <T> Long zrem(String key, T value) {
            return ops.opsForZSet().remove(key, value);
        }

        public <T> Long zrem(String key, Collection<T> values) {
            return ops.opsForZSet().remove(key, values);
        }

        public Long zcount(String key, double min, double max) {
            return ops.opsForZSet().count(key,min, max);
        }

        public Long zcount(String key) {
            return ops.opsForZSet().count(key,Double.MIN_VALUE, Double.MAX_VALUE);
        }

        public void expire(String key, long timeout, TimeUnit timeUnit) {
            ops.expire(key, timeout, timeUnit);
        }

        public void del(String key) {
            ops.delete(key);
        }

        public void del(Collection<String> keys) {
            ops.delete(keys);

        }
    }

    public static class Entry<K, V> {
        private K key;
        private V value;

        public Entry(K key, V value) {
            this.key = key;
            this.value = value;
        }

        public void set(K key, V value) {
            this.key = key;
            this.value = value;
        }

        public K getKey() {
            return key;
        }

        public V getValue() {
            return value;
        }
    }
}
